﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Linq;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using Dapper;
using Microsoft.CSharp.RuntimeBinder;
using Microsoft.Data.SqlClient;
using static Asion.Dapper.SqlMapperExtensions;
using static Dapper.SqlMapper;

namespace Asion.Dapper
{
    /// <summary>
    /// Main class for Dapper.SimpleCRUD extensions
    /// </summary>
    public static partial class SqlMapperExtensions
    {
      /*  public static Task<TEntity> GetAsync<TEntity>(this IDbConnection connection, IDictionary<string, string> dic, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var currenttype = typeof(T);
            //get select sql
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetSelectSql(currenttype, dic, WhereComandType.Dictionary);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("Get<{0}>: {1} with Id: {2}", currenttype, sql, dic));
            var para = new DynamicParameters(dic);
            return connection.QueryFirstAsync<TEntity>(sql, para, transaction, commandTimeout);
        }

        /// <summary>
        /// <para>By default queries the table matching the class name</para>
        /// <para>-Table name can be overridden by adding an attribute on your class [Table("YourTableName")]</para>
        /// <para>whereConditions is an anonymous type to filter the results ex: new {Category = 1, SubCategory=2}</para>
        /// <para>Supports transaction and command timeout</para>
        /// <para>Returns a list of entities that match where conditions</para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="connection"></param>
        /// <param name="whereConditions"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <returns>Gets a list of entities with optional exact match where conditions</returns>
        public static Task<IEnumerable<TEntity>> GetListAsync<TEntity>(this IDbConnection connection, object whereConditions, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var currenttype = typeof(T);
            //get select sql
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetSelectSql(currenttype, whereConditions, WhereComandType.Dictionary);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("GetList<{0}>: {1}", currenttype, sql));
            return connection.QueryAsync<TEntity>(sql, whereConditions, transaction, commandTimeout);
        }


        public static Task<IEnumerable<TEntity>> GetListAsync<TEntity>(this IDbConnection connection, string conditions, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var currenttype = typeof(T);
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetSelectSql(currenttype, conditions, WhereComandType.Text);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("GetList<{0}>: {1}", currenttype, sql));

            return connection.QueryAsync<TEntity>(sql, parameters, transaction, commandTimeout);
        }



        public static async Task<Page<TEntity>> GetListPageAsync<TEntity>(this IDbConnection connection, int pageNumber, int rowsPerPage, string conditions, string orderby, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
        {

            if (pageNumber < 1)
                throw new Exception("Page must be greater than 0");

            var currenttype = typeof(T);
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetPagedListSql(currenttype, pageNumber, rowsPerPage, conditions, orderby);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("GetList<{0}>: {1}", currenttype, sql));
            var girdResult = await connection.QueryMultipleAsync(sql, parameters, transaction, commandTimeout).ConfigureAwait(false);
            var rows = girdResult.Read<TEntity>();
            var total = girdResult.ReadFirst<int>();
            var result = new Page<TEntity>(pageNumber, rowsPerPage, total, rows);
            return result;

        }

        #region insert

        public static Task<int> InsertAsync<TEntity>(this IDbConnection connection, TEntity entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            return InsertAsync<int, TEntity>(connection, entityToInsert, transaction, commandTimeout);
        }


        public static async Task<TKey> InsertAsync<TKey, TEntity>(this IDbConnection connection, TEntity entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            //当TEntity是一个接口的时候
            var modelBaseType = typeof(TEntity);
            var baseType = typeof(TKey);
            //获取可空类型的基础类型
            var underlyingType = Nullable.GetUnderlyingType(baseType);
            var keytype = underlyingType ?? baseType;
            if (keytype != typeof(int) && keytype != typeof(uint) && keytype != typeof(long) && keytype != typeof(ulong) && keytype != typeof(short) && keytype != typeof(ushort) && keytype != typeof(Guid) && keytype != typeof(string))
            {
                throw new Exception("Invalid return type");
            }
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetInsertSql(modelBaseType, true);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("Insert: {0}", sql));

            var r = await connection.QueryAsync(sql, entityToInsert, transaction).ConfigureAwait(false);

            return (TKey)r.First().Id;
        }



        public static Task<int> BatchInsertAsync<TEntity>(this IDbConnection connection, IList<TEntity> entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            //当TEntity是一个接口的时候
            var modelBaseType = typeof(TEntity);
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetInsertSql(modelBaseType);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("# Method :BatchInsert # SQL :{0}", sql));

            return connection.ExecuteAsync(sql, entityToInsert, transaction, commandTimeout);
        }




        #endregion

        #region update

        public static Task<int> UpdateAsync<TEntity>(this IDbConnection connection, TEntity entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var baseModelType = typeof(TEntity);
            //构建基础更新架构
            var builder = SqlBuilderFactory.Create<TEntity>();
            var nullColumns = AssemblyHelper.GetNullColumns(entityToUpdate);
            var sql = builder.GetUpdateSql(entityToUpdate, nullColumns:nullColumns);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("Update: {0}", sql));
            return connection.ExecuteAsync(sql, entityToUpdate, transaction, commandTimeout);
        }




        #endregion

        /// <summary>
        /// <para>Deletes a record or records in the database that match the object passed in</para>
        /// <para>-By default deletes records in the table matching the class name</para>
        /// <para>Table name can be overridden by adding an attribute on your class [Table("YourTableName")]</para>
        /// <para>Supports transaction and command timeout</para>
        /// <para>Returns the number of records affected</para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="connection"></param>
        /// <param name="entityToDelete"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <returns>The number of records affected</returns>
        public static Task<int> DeleteAsync<TEntity>(this IDbConnection connection, IDictionary<string, string> dic, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var type = typeof(T);

            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetDeleteSql(type, dic, WhereComandType.Dictionary);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("Delete: {0}", sql));
            var dynmic = new DynamicParameters(dic);
            return connection.ExecuteAsync(sql, dynmic, transaction, commandTimeout);
        }


        public static Task<int> DeleteAsync<TEntity>(this IDbConnection connection, object id, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var currenttype = typeof(T);
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetDeleteSql(currenttype, id, WhereComandType.List);
            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("Delete<{0}> {1}", currenttype, sql));

            return connection.ExecuteAsync(sql, id, transaction, commandTimeout);
        }

        /// <summary>
        /// <para>Deletes a list of records in the database</para>
        /// <para>By default deletes records in the table matching the class name</para>
        /// <para>-Table name can be overridden by adding an attribute on your class [Table("YourTableName")]</para>
        /// <para>Deletes records where that match the where clause</para>
        /// <para>whereConditions is an anonymous type to filter the results ex: new {Category = 1, SubCategory=2}</para>
        /// <para>The number of records affected</para>
        /// <para>Supports transaction and command timeout</para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="connection"></param>
        /// <param name="whereConditions"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <returns>The number of records affected</returns>
        public static Task<int> DeleteListAsync<TEntity>(this IDbConnection connection, object whereConditions, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var currenttype = typeof(T);
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetDeleteSql(currenttype, whereConditions, WhereComandType.Dictionary);

            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("DeleteList<{0}> {1}", currenttype, sql));

            return connection.ExecuteAsync(sql, whereConditions, transaction, commandTimeout);
        }

        public static Task<int> DeleteListAsync<TEntity>(this IDbConnection connection, string conditions, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var masterSb = new StringBuilder();

            if (string.IsNullOrEmpty(conditions))
                throw new ArgumentException("DeleteList<TEntity> requires a where clause");
            if (!conditions.ToLower().Contains("where"))
                throw new ArgumentException("DeleteList<TEntity> requires a where clause and must contain the WHERE keyword");

            var currenttype = typeof(T);
            var builder = SqlBuilderFactory.Create<TEntity>();
            var sql = builder.GetDeleteSql(currenttype, conditions, WhereComandType.Text);


            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("DeleteList<{0}> {1}", currenttype, sql));

            return connection.ExecuteAsync(masterSb.ToString(), parameters, transaction, commandTimeout);
        }

        /// <summary>
        /// <para>By default queries the table matching the class name</para>
        /// <para>-Table name can be overridden by adding an attribute on your class [Table("YourTableName")]</para>
        /// <para>Returns a number of records entity by a single id from table T</para>
        /// <para>Supports transaction and command timeout</para>
        /// <para>whereConditions is an anonymous type to filter the results ex: new {Category = 1, SubCategory=2}</para>
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="connection"></param>
        /// <param name="whereConditions"></param>
        /// <param name="transaction"></param>
        /// <param name="commandTimeout"></param>
        /// <returns>Returns a count of records.</returns>
        public static Task<int> RecordCountAsync<TEntity>(this IDbConnection connection, string conditions = "", object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null)
        {
            var currenttype = typeof(T);
            var name = GetTableNameCache(currenttype);
            var sb = new StringBuilder();
            sb.Append("Select count(1)");
            sb.AppendFormat(" from {0}", name);
            sb.Append(" " + conditions);

            if (Debugger.IsAttached)
                Trace.WriteLine(String.Format("RecordCount<{0}>: {1}", currenttype, sb));

            return connection.ExecuteScalarAsync<int>(sb.ToString(), parameters, transaction, commandTimeout);
        }*/









    }
}

